﻿#pragma once
#include "AsyncCallback.h"
#include "FrameBuffer.h"


#pragma comment (lib,"Mmdevapi.lib")
#pragma comment (lib,"Mfplat.lib")

using namespace Microsoft::WRL;
using namespace concurrency;

#define CHECK_RESULT(hr_value,goto_lable) \
	if (FAILED(##hr_value))\
{\
	goto goto_lable##;\
}

// TODO: Implement error logging below
#define CHECK_AND_LOG(hr_value) \
	if (FAILED(##hr_value))\
{\
	assert(SUCCEEDED(hr_value));\
}

#define CHECK_AND_THROW(hr_value) \
	if (FAILED(##hr_value))\
{\
	throw ref new Platform::COMException(##hr_value##);\
}

#define CHECK_AND_THROW_WIN32(condition)\
	if (condition)\
{\
	HRESULT hError = __HRESULT_FROM_WIN32(GetLastError());\
	throw ref new Platform::COMException(hError); \
}


namespace GuitarFX_Audio
{

	public enum class AudioFXStateEnum
	{
		UnInitialized,
		Ready,
		Starting,
		Started,
		Stopping,
		InError
	};

	const LONG CAPTURE_TASK_PRIORITY = 2;
	const LONG PROCESS_TASK_PRIORITY = 1;
	const LONG RENDER_TASK_PRIORITY = 0;
	const LONG WORK_QUEUE_BASE_PRIORITY = 0;

	const float MAX_DELAY_HNS = 10000000;	// Allow max 1s delay

    public ref class AudioFX sealed
    {
	private:
		AudioFXStateEnum m_State;

		Platform::String ^m_CaptureDeviceId;
		Platform::String ^m_RenderDeviceId;

		ComPtr<IAudioClient2> m_CaptureClient;
		ComPtr<IAudioClient2> m_RenderClient;

		ComPtr<IAudioCaptureClient> m_Capture;
		ComPtr<IAudioRenderClient> m_Render;


		IMPLEMENT_MFASYNC_CALLBACK(AudioFX, CaptureCallback);
		ComPtr<IMFAsyncResult> m_CaptureCallbackResult;
		MFWORKITEM_KEY m_CaptureCallbackKey;

		IMPLEMENT_MFASYNC_CALLBACK(AudioFX, ProcessDataCallback);
		ComPtr<IMFAsyncResult> m_ProcessCallbackResult;
		MFWORKITEM_KEY m_ProcessDataCallbackKey;

		DWORD m_dwMFAudioWorkQueueId;

		HANDLE m_hCaptureCallbackEvent;

		WAVEFORMATEX *m_pCaptureFormat;


		UINT64 m_u64ExpectedCapturePosition;

		CFrameBuffer<float> m_CaptureBuffer;
		CFrameBuffer<float> m_RenderBuffer;

		float m_MasterVolume;
		float m_DelayFeedback;
		int m_DelayFrameOffset;	// number of samples to go back
		Windows::Foundation::TimeSpan m_DelayTime;	// Delay value in time units

		static task<bool> SupportsRawStream(Platform::String ^deviceId);
		static HRESULT InitAudioClient(const ComPtr<IAudioClient2> audioClient,const WAVEFORMATEX *pFormat,const DWORD streamFlags);
		
		void InitObjects();
		void InitWorkQ();
		void InitCapture(AUDCLNT_STREAMOPTIONS options);
		void InitRender(AUDCLNT_STREAMOPTIONS options);
		void InitBuffers();

		AudioFX(Platform::String ^captureDeviceId,Platform::String ^renderDeviceId,ComPtr<IAudioClient2> captureClient,ComPtr<IAudioClient2> renderClient);
		
    public:
		virtual ~AudioFX();

		Windows::Foundation::IAsyncAction ^InitializeAsync();

		// Create AudioFX for specific input and output devices
		static Windows::Foundation::IAsyncOperation<AudioFX ^> ^CreateAsync(Platform::String ^captureDeviceId, Platform::String ^renderDeviceId);


		void Start();
		void Stop();

		property AudioFXStateEnum State
		{
		public: AudioFXStateEnum get()
			{
				return m_State;
			}
		private: void set(AudioFXStateEnum value)
			{
				m_State = value;
			}
		}

		property float MasterVolume
		{
			float get()
			{
				return m_MasterVolume;
			}
			void set(float value)
			{
				m_MasterVolume = value;
			}
		}

		property float DelayFeedback
		{
			float get()
			{
				return m_DelayFeedback;
			}
			void set(float value)
			{
				if (value < 0 || value > 1)
					throw ref new Platform::InvalidArgumentException();

				m_DelayFeedback = value;
			}
		}

		property Windows::Foundation::TimeSpan DelayTime
		{
			Windows::Foundation::TimeSpan get()
			{
				return m_DelayTime;
			}
			void set(Windows::Foundation::TimeSpan value)
			{
				if (value.Duration < 0 || value.Duration > MAX_DELAY_HNS)
					throw ref new Platform::InvalidArgumentException();

				m_DelayTime = value;

				if (m_pCaptureFormat != nullptr)
					m_DelayFrameOffset = (int) (-value.Duration * m_pCaptureFormat->nSamplesPerSec / 10000000);
			}
		}

		// Platform startup
		static void Startup();
		// Platform shutdown - call on exit
		static void Shutdown();
	};
}